/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include <gdbus.h>

#include "connman.h"

#define	_DBG_AGENT(fmt, arg...)	DBG(DBG_AGENT, fmt, ## arg)

struct connman_agent {
	guint watch;
	gchar *path;
	gchar *sender;
};

static DBusConnection *connection = NULL;
static struct connman_agent *agent = NULL;

static void agent_free(struct connman_agent *a)
{
	g_free(a->sender);
	g_free(a->path);
	g_free(a);

	if (a == agent)
		agent = NULL;
}

static void agent_disconnect(DBusConnection *connection, void *data)
{
	struct connman_agent *agent = data;

	_DBG_AGENT("agent %p", agent);

	agent_free(agent);
}

int __connman_agent_register(const char *sender, const char *path)
{
	_DBG_AGENT("sender %s path %s", sender, path);

	if (agent != NULL) {
		return -EEXIST;
	}

	agent = g_try_new0(struct connman_agent, 1);
	if (agent == NULL) {
		return -ENOMEM;
	}

	agent->sender = g_strdup(sender);
	agent->path = g_strdup(path);

	agent->watch = g_dbus_add_disconnect_watch(connection, sender,
					agent_disconnect, agent, NULL);
	return 0;
}

static void __connman_agent_release(struct connman_agent *agent)
{
	DBusMessage *message;

	_DBG_AGENT("sender %s path %s", agent->sender, agent->path);

	message = dbus_message_new_method_call(agent->sender, agent->path,
					CONNMAN_AGENT_INTERFACE, "Release");
	if (message != NULL) {
		dbus_message_set_no_reply(message, TRUE);
		g_dbus_send_message(connection, message);
	}
}

int __connman_agent_unregister(const char *sender, const char *path)
{
	_DBG_AGENT("sender %s path %s", sender, path);

	/* TODO(sleffler) verify sender+path match */

	__connman_agent_release(agent);

	if (agent->watch > 0)
		g_dbus_remove_watch(connection, agent->watch);
	agent_free(agent);

	return 0;
}

struct request_input_reply {
	struct connman_service *service;
	connman_agent_cb_t callback;
	void *user_data;
};

static void request_input_reply(DBusPendingCall *call, void *user_data)
{
	struct request_input_reply *request_reply = user_data;
	DBusMessage *reply = dbus_pending_call_steal_reply(call);

	if (dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_ERROR) {
		DBusMessageIter iter, dict;

		dbus_message_iter_init(reply, &iter);
		dbus_message_iter_recurse(&iter, &dict);

		_DBG_AGENT("service %p user_data %p",
		    request_reply->service, request_reply->user_data);

		request_reply->callback(request_reply->service,
					request_reply->user_data,
					&dict);
	} else {
		_DBG_AGENT("ERROR %s, service %p user_data %p",
		    dbus_message_get_error_name(reply),
		    request_reply->service, request_reply->user_data);

		request_reply->callback(request_reply->service,
					request_reply->user_data,
					NULL);
	}
	connman_service_unref(request_reply->service);
	dbus_message_unref(reply);
	g_free(request_reply);
}

int __connman_agent_request_input(struct connman_service *service,
	const char *params[], connman_agent_cb_t callback, void *user_data)
{
	DBusMessage *message;
	const char *path;
	DBusMessageIter iter;
	DBusMessageIter dict;
	DBusPendingCall *call;
	struct request_input_reply *request_reply;
	const char *key, *value;
	int i;

	_DBG_AGENT("agent %p service %p callback %p user_data %p",
	    agent, service, callback, user_data);

	if (agent == NULL) {
		connman_error("%s: no agent registered", __func__);
		return -EINVAL;
	}

	message = dbus_message_new_method_call(agent->sender, agent->path,
					CONNMAN_AGENT_INTERFACE,
					"RequestInput");
	if (message == NULL) {
		connman_error("%s: no memory", __func__);
		return -ENOMEM;
	}

	dbus_message_iter_init_append(message, &iter);

	path = __connman_service_get_path(service);
	dbus_message_iter_append_basic(&iter,
				DBUS_TYPE_OBJECT_PATH, &path);

	connman_dbus_dict_open(&iter, &dict);
	for (i = 0; (key = params[i]) != NULL; i += 2) {
		value = params[i+1];
		if (value == NULL)
			value = "";
		_DBG_AGENT("key %s value %s", key,
			   connman_log_get_masked_value(key, value));
		connman_dbus_dict_append_basic(&dict, key,
					DBUS_TYPE_STRING, &value);
	}
	connman_dbus_dict_close(&iter, &dict);

	request_reply = g_try_new0(struct request_input_reply, 1);
	if (request_reply == NULL) {
		dbus_message_unref(message);
		return -ENOMEM;
	}

	/* TODO(sleffler) deal with timeouts */
	if (dbus_connection_send_with_reply(connection, message,
				&call, DBUS_TIMEOUT_INFINITE) == FALSE) {
		dbus_message_unref(message);
		g_free(request_reply);
		return -ESRCH;
	}

	if (call == NULL) {
		dbus_message_unref(message);
		g_free(request_reply);
		return -ESRCH;
	}

	_DBG_AGENT("setup notify, reply %p", request_reply);

	request_reply->service = connman_service_ref(service);
	request_reply->callback = callback;
	request_reply->user_data = user_data;

	dbus_pending_call_set_notify(call, request_input_reply,
				request_reply, NULL);

	dbus_message_unref(message);

	return 0;
}

int __connman_agent_init(void)
{
	_DBG_AGENT("");

	connection = connman_dbus_get_connection();
	return (connection == NULL ? -1 : 0);
}

void __connman_agent_cleanup(void)
{
	_DBG_AGENT("agent %p", agent);

	if (agent != NULL)
		__connman_agent_unregister(agent->sender, agent->path);
	if (connection != NULL)
		dbus_connection_unref(connection);
}
